package mt.dummy;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import mt.Order;
import mt.comm.ClientSideMessage;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;
/**
 * ServerCommDummy is used by the server to communicate with the clients.
 * @author Pedro,Rita, Miguel
 *
 */
public class ServerCommDummy implements ServerComm {
	/**
	 * The Port of the ServerCommDummy
	 */
	private static int PORT;

	/**
	 * The ServerSocket of the ServerCommDummy
	 */
	private ServerSocket serverSocket;

	/**
	 * The queue that has ServerMessage
	 */
	public BlockingQueue<ServerMessage> q = new ArrayBlockingQueue<ServerMessage>(100);

	/**
	 * The hashmap that has the nickname of the client and the output to answer him.
	 */

	public HashMap <String, ObjectOutputStream> maps = new HashMap<String,ObjectOutputStream>();

	private boolean stopSignal;


	public ServerCommDummy(int port) {
		PORT = port;
	}

	@Override
	public void start() {
		stopSignal = false;
		new StartServicing().start();
	}
	/**
	 *  StartServicing setups a server socket and stars another thread that listens for connections.
	 */
	class StartServicing extends Thread {

		private ObjectOutputStream out;
		private ObjectInputStream in;

		@Override
		public void run() {
			try {
				serverSocket = new ServerSocket(PORT);

				while (!stopSignal) { // espera q venha clients
					Socket socket = serverSocket.accept(); //
					out = new ObjectOutputStream(socket.getOutputStream());
					in = new ObjectInputStream(socket.getInputStream());
					new DealWithClient(socket, in, out).start();
				}
			} catch (IOException e) {
				stopSignal = true;
				try {
					if(in != null)
						in.close();
					if(out != null)
						out.close();
				} catch (IOException ex) {}
			}
		}
	}
	/**
	 *  This thread listens for connections. This Threads receives and processed the ServerMessage.
	 */


	class DealWithClient extends Thread {

		private final ObjectInputStream in;
		private final ObjectOutputStream out;
		private final Socket socket;
		private String clientName;

		public DealWithClient(Socket socket, ObjectInputStream in, ObjectOutputStream out) {
			this.socket = socket;
			this.in = in;
			this.out = out;
		}

		public ObjectOutputStream getOut(){
			return out;
		}

		@Override
		public void run() {
			try {
				while (socket.isConnected()) {

					ServerMessage message = (ServerMessage) in.readObject();


					switch (message.getType()) {
					case CONNECTED:

						if(!maps.containsKey(message.getSenderNickname())){
							clientName = message.getSenderNickname();

							maps.put(message.getSenderNickname(), out);
							q.add(message);
						}
						else
						{
							ClientMessage msg = new ClientMessage(ClientSideMessage.Type.ERROR,  "Nickname is in use.");
							out.writeObject(msg);
						}
						break;

					case NEW_ORDER:
						q.add(message);
						break;
					case DISCONNECTED:
						in.close();
						out.close();
						socket.close();
						maps.remove(message.getSenderNickname());
						System.out.println("<Server> Client '" + message.getSenderNickname() + "' disconnected.");
						return;
					default:
						break;
					}
				}

			} catch (IOException e) {
				ServerMessage msg = new ServerMessage(ServerSideMessage.Type.DISCONNECTED, clientName, null);
				q.clear();
				q.add(msg);
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public boolean hasNextMessage() {
		return !q.isEmpty();
	}

	@Override
	public ServerSideMessage getNextMessage() {
		return q.poll();
	}

	@Override
	public void sendError(String toNickname, String error) {
		ObjectOutputStream out = maps.get(toNickname);
		try {
			out.writeObject(new ClientMessage(ClientSideMessage.Type.ERROR, error));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		return maps.containsKey(nickname);
	}

	@Override
	public void disconnectClient(String nickname) {
		maps.remove(nickname);
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		if(clientIsConnected(receiversNickname)){
			ObjectOutputStream out= maps.get(receiversNickname);
			try {
				out.writeObject(new ClientMessage(ClientSideMessage.Type.ORDER,order));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void broadcastOrder(Order order){
		for (ObjectOutputStream out : maps.values()) {
			try {
				out.writeObject(new ClientMessage(ClientSideMessage.Type.ORDER,order));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}